package top.haijunit.work.core.doamin;

import cn.hutool.core.text.CharSequenceUtil;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @author zhanghaijun
 * @date 2024/6/30 18:54
 * @description [一句话描述该类的功能]
 */
@Slf4j
@UtilityClass
public class TreeNodeUtil {

    private static final String PARENT_NAME = "parent";
    private static final String CHILDREN_NAME = "children";

    public static <T extends TreeNode<T, Integer>> Collection<T> buildTree(Collection<T> dataList) {
        return buildTree(dataList, Collections.singletonList(0), item -> true);
    }

    public static <T extends TreeNode<T, S>, S> Collection<T> buildTree(Collection<T> dataList, Collection<S> parentIds) {
        return buildTree(dataList, parentIds, item -> true);
    }

    /**
     * 数据集合构建成树形结构 （ 注: 如果最开始的 parentIds 不在 dataList 中，不会进行任何处理 ）
     *
     * @param dataList  数据集合
     * @param parentIds 父元素的 Id 集合
     * @param map       调用者提供 UnaryOperator<T> 由调用着决定数据最终呈现形势
     * @param filter    调用者提供 Predicate<T> false 表示过滤 （ 注: 如果将父元素过滤掉等于剪枝 ）
     * @param <T>       extends ITreeNode
     */
    public static <T extends TreeNode<T, S>, S> Collection<T> buildTree(Collection<T> dataList, Collection<S> parentIds, Predicate<T> filter) {
        if (CollectionUtils.isEmpty(parentIds)) {
            return Collections.emptyList();
        }
        // 1. 将数据分为 父子结构
        Map<String, List<T>> nodeMap = dataList.stream().filter(filter).collect(Collectors.groupingBy(item -> parentIds.contains(item.getParentId()) ? PARENT_NAME : CHILDREN_NAME));

        Collection<T> parent = nodeMap.getOrDefault(PARENT_NAME, Collections.emptyList());
        Collection<T> children = nodeMap.getOrDefault(CHILDREN_NAME, Collections.emptyList());
        // 1.1 如果未分出或过滤了父元素则将子元素返回
        if (parent.isEmpty()) {
            return children;
        }
        // 2. 使用有序集合存储下一次变量的 parentIds
        Collection<S> nextIds = new ArrayList<>(dataList.size());
        // 3. 遍历父元素 以及修改父元素内容
        for (T parentItem : parent) {
            // 3.1 如果子元素已经加完，直接进入下一轮循环
            if (nextIds.size() == children.size()) {
                break;
            }
            // 3.2 过滤出 parent.id == children.parentId 的元素
            children.stream().filter(childrenItem -> parentItem.getId().equals(childrenItem.getParentId())).forEach(childrenItem -> {
                // 3.3 这次的子元素为下一次的父元素
                nextIds.add(childrenItem.getParentId());
                // 3.4 添加子元素到 parentItem.children 中
                try {
                    parentItem.getChildren().add(childrenItem);
                } catch (Exception e) {
                    log.warn("""
                        TreeNodeUtil 发生错误, 传入参数中 children 不能为 null，解决方法:\s
                        方法一、在map（推荐）或filter中初始化\s
                        方法二、Collection<T> children = new ArrayList<>()\s
                        方法三、初始化块对属性赋初值\s
                        方法四、构造时对属性赋初值""", e);
                }
            });
        }
        buildTree(children, nextIds, filter);
        return parent;
    }

    /**
     * 获取指定节点在树形结构中的路径
     *
     * @param dataList  数据集合
     * @param currentId 当前节点
     * @param function  调用者提供 Function<T, String> 用于指定当前节点如何呈现
     * @param <T>       extends ITreeNode
     * @return 指定节点在树形结构中的路径
     */
    public static <T extends TreeNode<T, S>, S> String generateTreePath(Collection<T> dataList, S currentId, Function<T, String> function) {
        if (null == currentId || CollectionUtils.isEmpty(dataList)) {
            return "";
        }
        Optional<T> optional = dataList.stream().filter(item -> item.getId().equals(currentId)).findFirst();
        if (optional.isEmpty()) {
            return "";
        }
        String path = generateTreePath(dataList, optional.get().getParentId(), function);
        if (CharSequenceUtil.isBlank(path)) {
            return function.apply(optional.get());
        } else {
            return CharSequenceUtil.join(".", path, function.apply(optional.get()));
        }
    }
}
